package net.siisise.io;

import java.io.*;

/**
 *  効率よくデータを封入するためのクラス。
 *  ストリーム型で利用する。
 *   Javaのシリアライゼーションにちょっと似ている。
 *   FIFOと同様の機能をクライアント/サーバ間で効率よく実現するために開発する。
 *   ファイル互換の上位バージョンはPacket2として別に開発してみるかもしれない。
 *
 *   アクセス方法はファイルシステム以上の概念を持つ。
 *
 *   読み書き2つのスレッドからの同時アクセスまでを保証する。
 *   スタック系のような任意の位置を読み書きする機能は持たない。
 *
 * 2006/11/9 GPLライセンスを適用
 *
 *   Copyright(C) 2001-2002,2006 siisise.net
 *
 *   @auther Masatoshi Sato <okome@siisise.net>
 */

public class Packet {
    byte[][] data;

    private final int buffersize = 64;
    
    int read;
    int write;
    int readp;
    /** データ長 */
    int length;
    String encode;
    
    
    public InputStream getInputStream() {
        InputStream in = new InputStream() {
            public int read() throws IOException {
                return Packet.this.read();
            }
            
            public int read(byte[] data, int offset, int length) {
                return Packet.this.read(data, offset, length);
            }
        
            public int available() {
                return length();
            }
        };
        return in;
    }
    
    public OutputStream getOutputStream() {
        OutputStream out = new OutputStream() {
            public void write(int data) {
                Packet.this.write((byte)data);
            }
            
            public void write(byte[] data, int offset, int length) {
                Packet.this.write(data, offset, length);
            }
        };
        return out;
    }
    
    /**
     * 微妙なクラス
     * 内部でwrite() を繰り返し呼び出す処理などがあるとデータが逆転するので
     * パイプにしない方がいい
     */
    public OutputStream getBackOutputStream() {
        OutputStream out = new OutputStream() {
            public void write(int data) {
                byte[] d = new byte[1];
                d[0] = (byte)data;
                Packet.this.bwrite(d);
            }
            
            /**
             * write(int) を呼び出すと逆順になってしまう
             */
            public void write(byte[] data, int offset, int length) {
                Packet.this.bwrite(data, offset, length);
            }
        };
        return out;
    }
    
    public Packet() {
        data = new byte[buffersize][];
        read = 0;
        readp = 0;
        write = 0;
        length = 0;
        encode = "Shift_JIS";
    }

    public void write(byte dt) {
        byte[] d;
        d = new byte[1];
        d[0] = dt;
        write(d);
    }
    
    /**
        書き込み
    */
    public void write(byte[] dt) {
        write(dt,0,dt.length);
    }

    /**
     * 書き込み基本.
     * dt の中身は変更されない
     * 長さが 0 の場合も対応している?   
     */
    public void write(byte[] dt, int st, int len) {
        if (read == ((write + 1) & (buffersize - 1))) { // 空きなし
            byte d2[] = new byte[length];
            read(d2);
            write(d2);
        }

        data[write] = new byte[dt.length];
        System.arraycopy(dt,0,data[write],st,len);
        write++;
        write &= (buffersize - 1);
        length += dt.length; // 読み許可
    }

    /**
     * このパケットのデータサイズを返す。
     * @deprecated length()
     */
    public int getSize() {
        return length;
    }
    
    public int length() {
        return length;
    }

    /**
     * 読みすぎたデータを書き戻したり、ヘッダを付けたり(要LOCK?)
     */
    public void bwrite(byte[] dt, int offset, int len)
    {
        byte[] d;
        if (read == ((write + 1) & (buffersize - 1))) {  // 空きなし
            d = new byte[length];
            read(d);
            write(dt, offset, len);
            write(d);
        } else {
            if (readp > 0) {
                if (len > readp) {
//                  System.arraycopy(dt, offset + len - 
                    d = new byte[len - readp];
                    System.arraycopy(dt, offset, d, 0, offset + len - readp);
                    System.arraycopy(dt, offset + len - readp, data[read], 0, readp);
                    readp = 0;
                    read --;
                    read &= (buffersize - 1);
                    data[read] = d;
                    length += len;
                } else {
                    System.arraycopy(dt, offset, data[read], readp - len, len);
                    readp -= len;
                    length += len;
                }
            } else { // 2002.9.9 追加修正
                d = new byte[len];
                System.arraycopy(dt,offset, d, 0, offset + len);
                read--;
                read &= (buffersize - 1);
                data[read] = d;
                length += len;
            }
        }
    }

    public void bwrite(byte[] dt) {
        bwrite(dt, 0, dt.length);
    }
    
    public void bwrite(int dt) {
        byte[] d = new byte[1];
        d[0] = (byte)dt;
        bwrite(d, 0, 1);
    }

    /**
     *  任意の位置にデータを加える
     *  p がフルサイズより大きいときは、最後に加える。
     *  新機能
     */
    void mwrite(int p, byte[] dt, int st, int len) {
        byte[] d1, d2;
        if (p < length()) {
            d1 = new byte[p];
        } else {
            d1 = new byte[length()];
        }
        read(d1);
        bwrite(dt,st,len);
        bwrite(d1);
    }

    /**
     * 中間または最後から取り出す。(未完成)
     * 読み出したデータはPacketから切り取られ消える。
     * test:
     *  p が 0 や - のときは?
     *  dt がnull やサイズ 0 のときは?
     *  Packet が短いときは?
     *  いずれもデータが破壊されることはない
     */
    int mread(int p, byte[] dt) {
        byte[] d;
        int len1, len2;
        d = new byte[p];
        len1 = read(d);
        len2 = read(dt);
        bwrite(d,0,len1);
        return len2;
    }

    /**
     * 最後から取る。
     * test:
     * da やpacket のサイズが0でも動作する
     * offset や len が範囲外では? 無理
     */
    public int read(byte[] dt, int offset, int len) {
        int rlen = 0;
        //int rp = 0;
        int siz;
        if (offset >= dt.length && len > 0) {
            return 0;
        }
        if (offset + len > dt.length) { // エラーにする?
            len = dt.length - offset;
        }
        
        while (length > 0 && (siz = data[read].length - readp) <= len - rlen) {
            System.arraycopy(data[read], readp, dt, offset + rlen, siz);
            rlen += siz;
            length -= siz;
            read ++;
            readp = 0;
            read &= (buffersize - 1);
        }
        if (length > 0 && dt.length - rlen > 0) {
            siz = dt.length - rlen;
            System.arraycopy(data[read], readp, dt, offset + rlen, siz);
            length -= siz;
            rlen += siz;
            readp += siz;
        }
        return rlen;
    }
    
    public int read(byte[] data) {
        return read(data, 0, data.length);
    }

    /**
     * 1バイト読み込み
     * -1 はデータなし
     * 使いたくない
    */
    public int read() {
        byte[] dt = new byte[1];
        int ret;
        if (read(dt) > 0) {
            ret = (int)dt[0] & 0xff;
        } else {
            ret = -1;
        }
        return ret;
    }

    public int readInt() {
        int i;
        i = read() << 24;
        i += (read() & 0xff) << 16;
        i += (read() & 0xff) << 8;
        i += (read() & 0xff);
        return i;
    }
    
    public void writeInt(int i) {
        byte[] dt = new byte[4];
        dt[0] = (byte)(i >> 24);
        dt[1] = (byte)(i >> 16);
        dt[2] = (byte)(i >> 8);
        dt[3] = (byte)(i);
        write(dt);
    }

    public void bwriteInt(int i) {
        byte[] dt = new byte[4];
        dt[0] = (byte)(i >> 24);
        dt[1] = (byte)(i >> 16);
        dt[2] = (byte)(i >> 8);
        dt[3] = (byte)(i);
        bwrite(dt);
    }

    public void write(String str) {
        byte[] dt;
        try {
            dt = str.getBytes(encode);
            writeInt(dt.length);
            write(dt);
        } catch (UnsupportedEncodingException e) {
        }
    }
    
    /**
     * 文字列読込み
     */
    public String readString() {
        int len = readInt();
        byte[] da = new byte[len];
        read(da);
        try {
            return new String(da, encode);
        } catch (IOException e) {
            return null;
        }
    }
    
    /**
     * 文字コードを指定する。
     */
    public void setEncode(String enc) {
        if (enc != null) {
            encode = enc;
        }
    }
    
    /**
     * 文字コードの取得。
     */
    public String getEncode() {
        return encode;
    }

    /**
    サブパケットの取り出し
    */
    public Packet readPacket() {
        int len;
        Packet pac;

        len = readInt();
        if (len <= length()) {
            byte[] d;
            pac = new Packet();
            d = new byte[len];
            read(d);
            pac.write(d);
        } else {
            bwriteInt(len);
            pac = null;
        }
        return pac;
    }

    /** パケットの書き込み
     サイズ情報を付け加えて、そのまま格納する。
     サイズ情報が二重にならないよう注意してください。
     元のPacketに(最終的には)変更は加えない。
    */
    void writePacket(Packet p) {
        byte[] dt;
        int size;
        size = p.length(); // サイズの確認
        writeInt(size); // サイズ書き込み
        dt = new byte[size];
        p.read(dt); // データ抜き出し
        p.write(dt); // データ書き戻し
        write(dt); // データ書き込み
    }

    /**
        複製を作成する。
    */
    public Packet copy() {
        Packet p;
        byte[] dt;
        dt = new byte[length()];
        read(dt);
        write(dt);
        p = new Packet();
        p.write(dt);
        return p;
    }
}
